package com.weilive.core.protocol.protobuf;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;

import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.MessageLite;
import com.weilive.core.protocol.PacketCodec;
import com.weilive.core.protocol.PacketError;
import com.weilive.core.protocol.protobuf.ProtobufPacket.PacketHead;
import com.weilive.core.protocol.protobuf.ProtobufPacket.Error;

public class ProtobufResponse {

	private final PacketHead.Builder builder = PacketHead.newBuilder();
	private MessageLite.Builder data = null;
	private PacketError error = null;

	public static ProtobufResponse create(MessageLite.Builder data) {
		return new ProtobufResponse(data);
	}

	public static ProtobufResponse create(PacketError error, MessageLite requestType) {
		return new ProtobufResponse(error, requestType);
	}

	private ProtobufResponse(MessageLite.Builder data) {
		this.builder.setFType(data.getDefaultInstanceForType().getClass().getSimpleName());
		this.data = data;
		bindRequest(ProtobufRequestLocal.getRequest());
	}

	private ProtobufResponse(PacketError code, MessageLite requestType) {
		this.builder.setFType(requestType.getClass().getSimpleName());
		this.error = code;
		bindRequest(ProtobufRequestLocal.getRequest());
	}

	public void setError(PacketError code) {
		this.error = code;
	}

	public void cleanError() {
		error = null;
	}

	public Integer getId() {
		return builder.hasFId() ? builder.getFId() : null;
	}

	public Integer getCodec() {
		return builder.hasFCodec() ? builder.getFCodec() : null;
	}

	public void setCodec(Integer codec) {
		if (codec != null) {
			builder.setFCodec(codec);
		} else {
			builder.clearFCodec();
		}
	}

	public String getType() {
		return builder.hasFType() ? builder.getFType() : null;
	}

	public MessageLite.Builder getData() {
		return data;
	}

	public PacketError getError() {
		return error;
	}

	private void bindRequest(ProtobufRequest request) {
		if (request != null) {
			if (request.getId() != null) {
				builder.setFId(request.getId());
			}
			if (request.getCodec() != null) {
				builder.setFCodec(request.getCodec());
			}
		}
	}

	public ChannelBuffer encode() throws Exception {
		byte[] encodeData = null;
		MessageLite packetData = null;
		int dataLength = 0;
		if (error != null) {
			Error.Builder errBuilder = builder.getFErrorBuilder();
			errBuilder.setFCode(error.getCode());
			if (error.getMessage() != null)
				errBuilder.setFMessage(error.getMessage());
		} else if (data != null) {
			if (builder.hasFCodec() && PacketCodec.serverIsGZIP(builder.getFCodec())) {
				encodeData = PacketCodec.encode(builder.getFCodec(), data.build().toByteArray());
				if (encodeData != null) {
					dataLength = encodeData.length;
				}
			} else {
				packetData = data.build();
				dataLength = packetData.getSerializedSize();
			}
		}
		MessageLite packetHead = builder.build();
		int headLen = packetHead.getSerializedSize();

		byte[] bytes = new byte[2 + headLen + dataLength];
		bytes[0] = (byte) ((headLen) & 0xff);
		bytes[1] = (byte) ((headLen >> 8) & 0xff);

		final CodedOutputStream headOutput = CodedOutputStream.newInstance(bytes, 2, headLen);
		packetHead.writeTo(headOutput);
		headOutput.checkNoSpaceLeft();

		if (encodeData != null) {
			System.arraycopy(encodeData, 0, bytes, 2 + headLen, dataLength);
		} else if (packetData != null) {
			final CodedOutputStream dataOutput = CodedOutputStream.newInstance(bytes, 2 + headLen, dataLength);
			packetData.writeTo(dataOutput);
			dataOutput.checkNoSpaceLeft();
		}
		return ChannelBuffers.wrappedBuffer(bytes);
	}

	@Override
	public String toString() {
		return String.format("{\"id\":%d, \"type\":\"%s\"%s%s}", builder.getFId(), builder.getFType(), error != null ? String.format(", \"error\":{\"code\":%d, \"message\":\"%s\"}", error.code, error.message) : "", data != null ? String.format(", \"data\":%s", data.build().toString()) : "");
	}
}
